// Copyright  2004-2008 Backplane Ltd.
//
// This file is part of Ubiquity formsPlayer
//
// Ubiquity formsPlayer is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
//
// Ubiquity formsPlayer is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License
// along with Ubiquity formsPlayer If not, see <http://www.gnu.org/licenses/>.
//	
//
//   Purpose: implementation of CPEValueElement.
//   $Id$

#include "stdafx.h"
 
#include "pevalueelement.h"

#include "XFormsApp.h"
#include <HTMLDOMTranslator.h>
#include "FP_Definitions.h"
#include "ElementWithValue.h"
extern CXFormsApp theApp;

const CComBSTR g_bsIXMLDOMDocument2SetPropertyParam_SelectionNamespaces("SelectionNamespaces");
const CComBSTR g_bsIXMLDOMDocument2SetPropertyParam_SelectionLanguage("SelectionLanguage");
const CComBSTR g_bsIXMLDOMDocument2SetPropertyParam_SelectionLanguageXPath("XPath");
CPEValueElement::CPEValueElement(void)
{
}

CPEValueElement::~CPEValueElement(void)
{
		//detachEvents();
}

HRESULT CPEValueElement::DocumentReady()
{
	HRESULT hr = __super::DocumentReady();
	if(m_pFormControl && m_pFormControl->m_pParentImpl)
	{
		CElementWithValue * pParent = dynamic_cast<CElementWithValue*>(m_pFormControl->m_pParentImpl->m_pXFormsElement);
		if(pParent)
		{
			pParent->valueElementReady(this);
			CComBSTR sClassNameToAdd;
			HRESULT _hr = pParent->GetInitialValueClassName(&sClassNameToAdd);
			if(S_OK == _hr)
			{
				CComPtr<IHTMLElement> pEl;
				GetMasterElement(&pEl);
				CComBSTR sCurrentClass;
				pEl->get_className(&sCurrentClass);
				sCurrentClass.Append(sClassNameToAdd);
				pEl->put_className(sCurrentClass);
			}
		}
	}
	return hr;
}


HRESULT CPEValueElement::GetLocallySpecifiedBindingId(CComBSTR & bsId)
{
	HRESULT hr = E_NOINTERFACE;
	CComPtr<IHTMLElement> pParentEl;
	GetParentElement(&pParentEl);
	if(pParentEl)
	{
		CComVariant vtRetVal;
		CComBSTR bsXBLAttributeName(L"xbl-binding");
		hr = pParentEl->getAttribute(bsXBLAttributeName,1,&vtRetVal);
		if(S_OK == hr && VT_BSTR == vtRetVal.vt)
		{
			bsId = vtRetVal.bstrVal;
		}
	}	
	return hr;
}

HRESULT CPEValueElement::getXML(IXMLDOMNode ** ppXML)
{
	HRESULT hr = E_NOINTERFACE;
	CComQIPtr<IHTMLDOMNode> pMaster = GetControllingUnknown();
	if(pMaster && m_pFormControl)
	{
		CComPtr<IXMLDOMNode> pParentXMLNode;
		if(m_pFormControl->m_pParentImpl)
		{
			CXFormsElement2 * pParent = dynamic_cast<CXFormsElement2*>(m_pFormControl->m_pParentImpl->m_pXFormsElement);
			if(pParent)
			{
				hr = pParent->getXML(&pParentXMLNode);
				if(SUCCEEDED(hr))
				{
					CComPtr<IXMLDOMDocument> pDoc;
					pParentXMLNode->get_ownerDocument(&pDoc);
					CComQIPtr<IXMLDOMDocument2> pDoc2(pDoc);
					if(pDoc2)
					{
						CComBSTR bsNamespaces = L"xmlns:xf=\"";
						bsNamespaces += theApp.m_sXFormsURI;
						bsNamespaces += L"\"";
						pDoc2->setProperty(g_bsIXMLDOMDocument2SetPropertyParam_SelectionLanguage,CComVariant(g_bsIXMLDOMDocument2SetPropertyParam_SelectionLanguageXPath));
						pDoc2->setProperty(g_bsIXMLDOMDocument2SetPropertyParam_SelectionNamespaces,CComVariant(bsNamespaces));
						hr = pParentXMLNode->selectSingleNode(L"xf:pe--value",ppXML);

						if(false)
						{
							CComBSTR bsXML;
							pParentXMLNode->get_xml(&bsXML);
							::MessageBoxW(0,bsXML,0,0);
						}
					}
				}
			}
			else
			{
				CHTMLDOMTranslator translator(CLSID_DOMDocument60);
				hr = translator.cloneNode(pMaster,&pParentXMLNode,true);
			}
		}
	}
	return hr;
}

//TODO: is this specialisation of this function necessary?
HRESULT CPEValueElement::InitBehaviour()
{
	RegisterName(g_bsAttrXFormsElementController);

	if(m_lFormKey)
		theApp.FormsManager().GetForm(m_lFormKey,&m_pXForms);
	if(m_pXForms)
		m_pXForms->AddDeletionListener(this);

	
	m_pImpl = CreateImpl();
	m_pFormControl = dynamic_cast<CFormControl*>(m_pImpl);
	return RegisterCustomEvents(sm_asEventNames,CPEVALUEELEMENT_EVENTS_LENGTH, m_alEventCookies);
}


HRESULT CPEValueElement::attachEvents( IHTMLDocument3 * pDoc3)
{
	HRESULT hr;
//TEMPDISABLE
	//	hr = __super::attachEvents( pDoc3);
	hr = attachParentEvents();

	return hr;
}

/// <summary>
/// <c>attachParentEvents()</c> attaches the specifed events of this object to its parent
/// HTML element.
/// </summary>
HRESULT CPEValueElement::attachParentEvents()
{
	HRESULT hr = E_FAIL;	
	CComPtr<IHTMLElement> pEl;
	hr = GetParentElement(&pEl);
	CComQIPtr<IHTMLElement2> pEl2 = pEl;
	// If the event sink hasn't been created, create it.
	if(!m_pEventSink) {
		CComObject<CEventSink> * pSink;
		hr = CComObject<CEventSink>::CreateInstance(&pSink);
		m_pEventSink = pSink;
	}

	if (SUCCEEDED(hr))
	{
		// Attach the events to the parent HTML element
		hr = __super::attachEvents( pEl2, &m_lstParentEvents);
	}

	return hr;
}

void CPEValueElement::populateEvents()
{
	__super::populateEvents();
	populateParentEvents();
}

void CPEValueElement::populateParentEvents()
{
	m_lstParentEvents.push_front(&EVENT_FP_PUTVALUE);
	m_lstParentEvents.push_front(&EVENT_FP_PUTFOCUS);
	m_lstParentEvents.push_front(&EVENT_FP_UIREFRESH);
}

HRESULT CPEValueElement::detachEvents()
{
	HRESULT hr = __super::detachEvents();
	hr = detachParentEvents();

	return hr;
}

HRESULT CPEValueElement::detachParentEvents()
{
	CComPtr<IHTMLElement> pParent;
	HRESULT hr = GetParentElement(&pParent);
    CComQIPtr<IHTMLElement2> pParent2 = pParent;

	if(pParent2)
	{
		CComBSTRPtrList::iterator i = m_lstParentEvents.begin();
		while(i != m_lstParentEvents.end() && SUCCEEDED(hr))
		{
			hr =  pParent2->detachEvent((*i)->m_str,m_pEventSink);
			++i;
		}
	}
	return hr;
}

HRESULT CPEValueElement::GetParentElement(IHTMLElement ** ppEl)
{
	CComPtr<IHTMLElement> pMasterEl;
	GetMasterElement(&pMasterEl);
	HRESULT hr;
	if(pMasterEl)
		hr = pMasterEl->get_parentElement(ppEl);
	else
		hr = E_FAIL;
	return hr;
}

HRESULT CPEValueElement::handleEvent(CComBSTR& sEventName, IHTMLEventObj2 * pEventObject,VARIANT * pvarResult)
{
	HRESULT hr = S_FALSE;
	
	if(sEventName == L"propertychange")
	{
		CComBSTR s;
		pEventObject->get_propertyName(&s);
        if(s == "value")
		{
			CComBSTR sVal;
			get_value(&sVal);

			onfp_valuechanged(sVal);
		}
		else if(s == "valuechanging")
		{
            CComBSTR sVal;
			get_valuechanging(&sVal);

			onfp_valuechanging(sVal);
		}
	} else if(sEventName == L"help") {
		onfp_hint();

		CComQIPtr<IHTMLEventObj> pEvObj0 = pEventObject;
			
		//disable the default IE HTML event processing for this event.
		pvarResult->boolVal = FALSE;
		pEvObj0->put_cancelBubble(VARIANT_TRUE);
		pEvObj0->put_returnValue(CComVariant(false));
	} else if(sEventName == L"blur") {
			onfp_focusout();
	} else if(sEventName == L"focus") {
			// The DOMFocusIn Event is now fired on the HTML focus event. The move 
			// is due to a problems with the cursor not moving with the focus when 
			// handled on the HTML focusin event.
			onfp_focusin();
	} else if(sEventName == L"keydown") {
		CComQIPtr<IHTMLEventObj> pEvObj0 = pEventObject;
		long lKey = 0;
		pEvObj0->get_keyCode(&lKey);
		if(lKey == 9)
		{
			VARIANT_BOOL bShift;
			pEvObj0->get_shiftKey(&bShift);
			if(bShift) // if shift & tab
				onfp_previous(); // fire previous event
			else
				onfp_next();
		}
	} else if(sEventName == L"keyup") {
		CComQIPtr<IHTMLEventObj> pEvObj0 = pEventObject;
		long lKey = 0;
		pEvObj0->get_keyCode(&lKey);
		if(lKey == 13) // if enter key
			onfp_activate();
	} else if(sEventName == L"fp-putvalue"){
		// Fired by parent
		// Get the value to pass to the UIScript
		BSTR sVal = 0;
		pEventObject->get_dataFld(&sVal);
		// Pass the value to the UIScript
		put_value(sVal);
        ::SysFreeString(sVal);
	} else if(sEventName == L"fp-putfocus"){
		// Fired by parent
		// Pass the focus to the UIScript
		focus();
	}  else {
		//Should not normally reach this point.
		::AtlTrace("'formsplayer.dll': unhandled event '%S' occured on control %X",sEventName,this);
	}

	return hr;
}

const CComBSTR *  const CPEValueElement::sm_asEventNames[] 
= 
{
	&EVENT_FP_VALUECHANGED,
	&EVENT_FP_VALUECHANGING,
	&EVENT_FP_FOCUSIN,
	&EVENT_FP_FOCUSOUT,
	&EVENT_FP_ACTIVATE,
	&EVENT_FP_HINT,
	&EVENT_FP_NEXT,
	&EVENT_FP_PREVIOUS,
	&EVENT_FP_UIREADY
};

#define EVENT_ARRAYPOSITION_FP_VALUECHANGED		0
#define EVENT_ARRAYPOSITION_FP_VALUECHANGING	1
#define EVENT_ARRAYPOSITION_FP_FOCUSIN			2
#define EVENT_ARRAYPOSITION_FP_FOCUSOUT			3
#define EVENT_ARRAYPOSITION_FP_ACTIVATE			4
#define EVENT_ARRAYPOSITION_FP_HINT				5
#define EVENT_ARRAYPOSITION_FP_NEXT				6
#define EVENT_ARRAYPOSITION_FP_PREVIOUS			7
#define EVENT_ARRAYPOSITION_FP_UIREADY			8

HRESULT CPEValueElement::onfp_valuechanged(BSTR bsVal)
{
	HRESULT hr = E_NOINTERFACE;
	CComPtr<IMutationEvent> pMutation;
	pMutation.CoCreateInstance(CLSID_MutationEvent);
	if(pMutation)
	{
		CComVariant pVar0(0l);
		hr = pMutation->initMutationEvent(EVENT_FP_VALUECHANGED,false,false,pVar0,0,bsVal,0,2);
		BOOL bCancelled;
		hr = dispatchEvent(pMutation,&bCancelled);
	}
	return hr;
}

HRESULT CPEValueElement::onfp_valuechanging(BSTR bsVal)
{
	HRESULT hr = E_NOINTERFACE;
	CComPtr<IMutationEvent> pMutation;
	pMutation.CoCreateInstance(CLSID_MutationEvent);
	if(pMutation)
	{
		CComVariant pVar0(0l);
		hr = pMutation->initMutationEvent(EVENT_FP_VALUECHANGING,false,false,pVar0,0,bsVal,0,2);
		BOOL bCancelled;
		hr = dispatchEvent(pMutation,&bCancelled);
	}
	
	return hr;
}

#define DOM2EVENTFIRER(className,evName,evNameConstant) \
	HRESULT className::on##evName(){\
		HRESULT hr = E_FAIL;\
		CComPtr<IDOMEvent> pEvent;\
		pEvent.CoCreateInstance(CLSID_DOMEvent);\
		if(pEvent){\
			hr = pEvent->initEvent(evNameConstant,false,false);\
			if(SUCCEEDED(hr)){BOOL bCancelled;\
			hr = dispatchEvent(pEvent,&bCancelled);}\
		}\
	return hr;}

DOM2EVENTFIRER(CPEValueElement,fp_focusin, EVENT_FP_FOCUSIN)
DOM2EVENTFIRER(CPEValueElement,fp_focusout, EVENT_FP_FOCUSOUT)
DOM2EVENTFIRER(CPEValueElement,fp_activate, EVENT_FP_ACTIVATE)
DOM2EVENTFIRER(CPEValueElement,fp_hint, EVENT_FP_HINT)
DOM2EVENTFIRER(CPEValueElement,fp_next, EVENT_FP_NEXT)
DOM2EVENTFIRER(CPEValueElement,fp_previous, EVENT_FP_PREVIOUS)
DOM2EVENTFIRER(CPEValueElement,fp_uiready, EVENT_FP_UIREADY)
